﻿using MultiLang;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Timers;
using System.Windows.Forms;
using Rsdn.Janus.Framework;
using Rsdn.Janus.ObjectModel;
using Rsdn.Shortcuts;
using Rsdn.SmartApp;
using Rsdn.TreeGrid;

namespace Rsdn.Janus
{
    internal partial class ForumDummyFormBase : UserControl, IFeatureView
    {
        #region Columns Id
        protected const int ColumnId = 0;
        protected const int ColumnMarker = 1;
        protected const int ColumnSubject = 2;
        protected const int ColumnAuthor = 3;
        protected const int ColumnRate = 4;
        protected const int ColumnSubjectRate = 5;
        protected const int ColumnAnswersCount = 6;
        protected const int ColumnDate = 7;
        #endregion

        #region StepDirection Enum
        public enum StepDirection
        {
            Up,
            Down
        }
        #endregion

        #region AttrType Enum
        public enum AttrType
        {
            Unread,
            UnreadAnswerToMe,
            Marked,
            Any
        }
        #endregion

        #region SearchMessageArea Enum
        public enum SearchMessageArea
        {
            CurrentTopic,
            CurrentForum,
            //Global
        }
        #endregion

        private readonly ServiceManager _serviceManager;
        private readonly AsyncOperation _asyncOperation;
        private StripMenuGenerator _menuGenerator;
        private MsgViewer _msgViewer; //it is Message viever on the bottom panel
        private readonly DragStartDetector _dragStartDetector = new DragStartDetector();
        private readonly IDisposable _eventsSubscription;

        // ReSharper disable ConvertToConstant, RedundantDefaultFieldInitializer
        [ExpectService]
        private readonly IForumImageManager _imageManager = null;

        private ForumFormState _state = ForumFormState.Normal;

        // ReSharper restore ConvertToConstant, RedundantDefaultFieldInitializer
        #region Фильтрация по темам в списке сообщений
        private bool _isFilterActive;

        private string _lastFilterValue = String.Empty;
        #endregion

        public event EventHandler SelectedMessagesChanged;

        #region Columns
        private System.Windows.Forms.ColumnHeader _idCol;
        private System.Windows.Forms.ColumnHeader _markedCol;
        private System.Windows.Forms.ColumnHeader _subjectCol;
        private System.Windows.Forms.ColumnHeader _userNickCol;
        private System.Windows.Forms.ColumnHeader _ratesCol;
        private System.Windows.Forms.ColumnHeader _ratesThisCol;
        private System.Windows.Forms.ColumnHeader _repliesCountCol;
        private System.Windows.Forms.ColumnHeader _msgDateCol;
        #endregion

        private IDisposable _aggregatesChangedSubscription;

        protected ForumDummyFormBase(IServiceProvider provider)
        {
            _serviceManager = new ServiceManager(provider);
            _asyncOperation = AsyncHelper.CreateOperation();

            ServiceManager.Publish<IDefaultCommandService>(new DefaultCommandService("Janus.Forum.ReplyMessage")); //MLHIDE

            this.AssignServices(provider);

            InitializeComponent();

            SetPositionViewMsgArea(Config.Instance.MsgPosition);
            CustomInitializeComponent();

            _eventsSubscription = EventBrokerHelper.SubscribeEventHandlers(this, ServiceManager);
            // IFeature activeFeature = Features.Instance.ActiveFeature;
            // _activeForum = Forums.Instance.ActiveForum;
            IForumsAggregatesService forumsAggregatesService = provider.GetService<IForumsAggregatesService>();
            _aggregatesChangedSubscription =
                forumsAggregatesService.AggregatesChanged.Subscribe(args => AggregatesChanged(args));
        }

        #region IFeatureView
        void IFeatureView.Activate(IFeature ifeature)
        {
            HideFilterPanel();

            //Note AN Why was commented? Duplicate call from InvokeActiveMessagesChanged()
            // duplicate call is not bigger problem as miising activate feature call

            Feature feature = (Feature)ifeature;
            Features.Instance.ActiveFeature = feature;
            FillMessages();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_menuGenerator != null)
                {
                    _menuGenerator.Dispose();
                }

                _eventsSubscription.Dispose();

                if (_aggregatesChangedSubscription != null)
                {
                    _aggregatesChangedSubscription.Dispose();
                    _aggregatesChangedSubscription = null;
                }

                StyleConfig.StyleChange -= OnStyleChanged;

                if (components != null)
                {
                    components.Dispose();
                }
            }

            base.Dispose(disposing);
        }

        /// <summary>
        /// Activates the view.
        /// Need for views switch
        /// </summary>
        public virtual void ActivateView()
        {
            ApplicationManager.Instance.ForumNavigator.MessageNavigated += MessageNavigated;
        }

        /// <summary>
        /// Deactivates the view.
        /// Need for views switch
        /// </summary>
        public virtual void DeactivateView()
        {
            ApplicationManager.Instance.ForumNavigator.MessageNavigated -= MessageNavigated;
            _tgMsgs.ActiveNode = null;
            OldMsgId = -1;
        }

        void IFeatureView.Refresh()
        {
            FillMessages();

            if (_isFilterActive)
            {
                ApplyCurrentFilter();
            }
        }

        void IFeatureView.ConfigChanged()
        {
            // TODO: надо добавить сюда все действия которые нужно сделать при изменении конфига.
            SetSetings();
            Forum forum = Features.Instance.ActiveFeature as Forum;
            if (forum != null)
            {
                forum.Refresh();
            }
            FillMessages();

            //Refresh();

            if (_isFilterActive)
            {
                ApplyCurrentFilter();
            }
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            ClearMessageView();
            ClearMessages();
            if (_msgViewer != null)
            {
                _msgViewer.Invalidate();
            }
            _tgMsgs.Invalidate();
        }

        private void SetSetings()
        {
            if (ActiveForum != null)
            {
                _tgMsgs.Indent = Config.Instance.ForumDisplayConfig.GridIndent;
                _tgMsgs.GridLines = Config.Instance.ForumDisplayConfig.MsgListGridLines;
            }
        }
        #endregion IFeatureView

        #region Инициализация

        protected virtual void UpdateColumnTitle(ColumnHeader columnHeader, ImageList imageList)
        {
            switch (columnHeader.Index)
            {
                case ColumnId:
                    columnHeader.Text = "Id";                         //MLHIDE
                    break;
                case ColumnMarker:
                    columnHeader.Text = "";

                    columnHeader.ImageIndex =
                        imageList.AddImage(_imageManager.GetMarkImage(MessageFlagExistence.OnMessage));
                    break;
                case ColumnSubject:
                    columnHeader.Text = SR.TGColumnSubject;
                    columnHeader.ImageIndex =
                        imageList.AddImage(
                            _imageManager.GetMessageImage(
                                MessageType.Ordinal,
                                MessageFlagExistence.None,
                                false,
                                MessageFlagExistence.None,
                                false));

                    break;
                case ColumnAuthor:
                    columnHeader.Text = SR.TGColumnAuthor;
                    columnHeader.ImageIndex =
                        imageList.AddImage(_imageManager.GetUserImage(UserClass.User));

                    break;
                case ColumnRate:
                    columnHeader.Text = SR.TGColumnRate;
                    break;
                case ColumnSubjectRate:
                    columnHeader.Text = SR.TGColumnSubjectRate;
                    break;
                case ColumnAnswersCount:
                    columnHeader.Text = SR.TGColumnAnswers;
                    break;
                case ColumnDate:
                    columnHeader.Text = SR.TGColumnDate;
                    columnHeader.ImageIndex =
                        imageList.AddImage(_imageManager.GetMessageDateImage(DateTime.Now));
                    break;
            }
        }

        protected virtual void AddGridColumns()
        {
            _idCol = new ColumnHeader();           // 0
            _markedCol = new ColumnHeader();       // 1
            _subjectCol = new ColumnHeader();      // 2
            _userNickCol = new ColumnHeader();     // 3
            _ratesCol = new ColumnHeader();        // 4
            _ratesThisCol = new ColumnHeader();    // 5
            _repliesCountCol = new ColumnHeader(); // 6
            _msgDateCol = new ColumnHeader();      // 7

            this._tgMsgs.Columns.AddRange(
                new[]
                    {
                        _idCol,
                        _markedCol,
                        _subjectCol,
                        _userNickCol,
                        _ratesCol,
                        _ratesThisCol,
                        _repliesCountCol,
                        _msgDateCol
                    });

            this._tgMsgs.ColumnsOrder = new int[]
                                            {
                                                0,
                                                1,
                                                2,
                                                3,
                                                4,
                                                5,
                                                6,
                                                7
                                            };

            this._tgMsgs.ColumnsWidth = new int[]
                                            {
                                                40,
                                                26,
                                                200,
                                                60,
                                                60,
                                                70,
                                                72,
                                                67
                                            };
        }

        private void CustomInitializeComponent()
        {
            StyleConfig.StyleChange += OnStyleChanged;
            // TODO  Look here for click changed
            this._tgMsgs.AfterActivateNode += _tgMsgs_AfterActivateNode;

            State = Config.Instance.ForumFormState;

            #region Columns
            AddGridColumns();

            #endregion

            // Делаем поддержку локализации для TreeGrid-а
            // В случае изменения TreeGrid-а в Designer-е, необходимо внести изменения сюда
            // Not anymore. All changes only in source
            int columnsCount = _tgMsgs.Columns.Count;
            ImageList imageList = new ImageList();
            imageList.ColorDepth = ColorDepth.Depth32Bit;

            for (int i = 0; i < columnsCount; i++)
            {
                UpdateColumnTitle(_tgMsgs.Columns[i], imageList);
            }

            _tgMsgs.SmallImageList = imageList;

            _tgMsgs.Font = Config.Instance.StyleConfig.MessageTreeFont;
            _tgMsgs.BackColor = Config.Instance.StyleConfig.MessageTreeBack;
            _tgMsgs.GridLines = Config.Instance.ForumDisplayConfig.MsgListGridLines;

            if (Config.Instance.ForumColumnOrder.Length == _tgMsgs.Columns.Count)
            {
                _tgMsgs.ColumnsOrder = Config.Instance.ForumColumnOrder;
            }

            if (Config.Instance.ForumColumnWidth.Length == _tgMsgs.Columns.Count)
            {
                _tgMsgs.ColumnsWidth = Config.Instance.ForumColumnWidth;
            }

            _msgViewer = new MsgViewer(ServiceManager);
            _msgViewer.Dock = DockStyle.Fill;

            _bottomPanel.Controls.Add(_msgViewer);

            //_btnResetFilter.Text = SR.Forum.ResetFilter;
        }

        #endregion

        #region Поддержка стилей
        private void OnStyleChanged(object s, StyleChangeEventArgs e)
        {
            UpdateStyle();
            Refresh();
        }

        private void UpdateStyle()
        {
            _tgMsgs.Font = Config.Instance.StyleConfig.MessageTreeFont;
            _tgMsgs.BackColor = Config.Instance.StyleConfig.MessageTreeBack;
        }
        #endregion

        #region Состояние

        public ForumFormState State
        {
            get
            {
                return _state;
            }
            set
            {
                if (_state == value)
                {
                    return;
                }

                switch (value)
                {
                    case ForumFormState.Normal:
                        _topPanel.Visible = false;
                        _bottomPanel.Visible = false;
                        _splitter.Visible = true;
                        break;
                    case ForumFormState.ListMaximized:
                        _topPanel.Visible = true;
                        _bottomPanel.Visible = false;
                        _splitter.Visible = false;
                        break;
                    case ForumFormState.MessageMaximized:
                        _topPanel.Visible = false;
                        _bottomPanel.Visible = true;
                        _splitter.Visible = false;
                        break;
                }

                _state = value;
                Config.Instance.ForumFormState = _state;
            }
        }
        #endregion


        #region Загрузка списка сообщений и самого сообщения

        #region FillMessages

        public bool SelectMessage(int msgID)
        {
            Forum forum = Features.Instance.ActiveFeature as Forum;

            if (forum == null)
            {
                return false;
            }

            _tgMsgs.ActiveNode = forum.FindMsgById(msgID);
            return _tgMsgs.ActiveNode != null;
        }

        public bool SelectMessage(int forumID, int msgID)
        {
            Forum activeForum = ActiveForum;

            if (activeForum != null && activeForum.ID == forumID)
            {
                return SelectMessage(msgID);
            }

            using (_tgMsgs.UpdateScope())
            {
                _tgMsgs.Nodes = null;
                var forum = Forums.Instance[forumID];
                Features.Instance.ActiveFeature = forum;
                _tgMsgs.Nodes = forum.Msgs;
                _tgMsgs.ActiveNode = forum.FindMsgById(msgID);

                return _tgMsgs.ActiveNode != null;
            }
        }

        /// <summary>
        /// Fills the messages.
        /// </summary>
        public void FillMessages()
        {
            var forum = Features.Instance.ActiveFeature as Forum;

            ClearMessages();
            ClearMessageView();

            if (forum == null)
            {
                return;
            }

            // При получении активного сообщения теперь может произойти
            // подчитывание несчитанных сообщений форум. При этом список
            // сообщений может устареть. Чтобы в _tgMsgs.Nodes не оказался
            // устаревший список сообщений нужно присваивать его после 
            // считывания активного сообщения.
            IMsg activeMsg = forum.ActiveMsg;
            // Внимание!!! Порядок считывания forum.ActiveMsg и forum.Msgs критичен!
            _tgMsgs.Nodes = null;
            _tgMsgs.Nodes = forum.Msgs; // here grid is filled

            if (activeMsg == null)
            {
                if (_tgMsgs.Nodes != null && _tgMsgs.Nodes.Count > 0)
                {
                    _tgMsgs.ActiveNodeIndex = 0;
                }
            }
            else
            {
                _tgMsgs.ActiveNode = activeMsg;
            }

            _markTimer.Enabled = false;
        }

        private void ClearMessages()
        {
            _tgMsgs.Nodes = null;
            _tgMsgs.ActiveNode = null;
        }


        protected override void OnVisibleChanged(EventArgs e)
        {
            base.OnVisibleChanged(e);

            // Какого-то хрена при Visible == false не удается установить 
            // активную ветку. Поэтому, в момент когда Visible становится равным true
            // нужно попытаться еще раз активировать нужную ветку.
            if (Visible)
            {
                IUIShell uiShell = _serviceManager.GetService<IUIShell>();
                uiShell.ActiveGrid = _tgMsgs;
                uiShell.ActiveFeatureView = this;

                Forum forum = Features.Instance.ActiveFeature as Forum;
                if (forum == null)
                {
                    return;
                }

                IMsg activeMsg = forum.ActiveMsg;
                if (activeMsg == null)
                {
                    if (_tgMsgs.Nodes != null && _tgMsgs.Nodes.Count > 0)
                    {
                        _tgMsgs.ActiveNodeIndex = 0;
                    }
                }
                else
                {
                    #region Удалить закомментированный код если ошибка не будет повторятся

                    //					TODO: удалить закомментированный код если ошибка не будет повторятся
                    //					try
                    //					{
                    _tgMsgs.ActiveNode = activeMsg;
                    //					}
                    //					catch (Exception ex)
                    //					{
                    //						// Ошибка "Строка не подключена к TreGrid!" возникает
                    //						// когда кто-то пытается сделать активной строку 
                    //						// отсуствующей в гриде в даннымй момент.
                    // // В Янусе эта ошибка обычно возникает, когда 
                    // // переходишь из форума X в список неотправленных 
                    // // сообщений, а затем в другой форум.
                    // // При этом в activeMsg уже содержится строка из нового
                    // // форума, а в _tgMsgs.Nodes еще содержатся сообщения
                    // // старого. Причем OnVisibleChanged вызывается как-то 
                    // // не явно (похоже из хука). Так что разруливайте
                    // // логику вызова OnVisibleChanged.
                    // // Пока что я в тупую скрываю данное исключение.
                    // // Вроде как дальше по ходу пьесы все делается нормально.
                    // // То есть в грид помещаются сообщения нового форума
                    // // и происходит повторная попытка активировать
                    // // ветку, которая должна быть активной.<br /> 
                    //						if (ex.Message != "Строка не подключена к TreGrid!")
                    //							throw;
                    //					}

                    #endregion
                }
            }
            else
            {
                IUIShell uiShell = _serviceManager.GetService<IUIShell>();
                uiShell.ActiveGrid = null;
                uiShell.ActiveFeatureView = null;
            }
        }
        #endregion

        #endregion

        private void AggregatesChanged(EventArgs arg)
        {
            // TODO; why Not called AggregatesChanged?
#if FRI_TRACE
            Trace.WriteLine("FRI: ForumDummyFormBase.treeGrid.Invalidate"); //MLHIDE
#endif

            _tgMsgs.Invalidate();
        }

        #region Shortcuts

        [ml_MethodShortcut((Shortcut)Keys.Space, 247,"Смарт-переход",248,"Прокрутить сообщение на страницу. При небходимости перейти к следующему сообщению.")]
        private void PageDown()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.SmartJump", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 249,"Адрес сообщения",250,"Скопировать адрес сообщения в буфер обмена.")]
        private void CopyMsgAddress()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.CopyMessageAddress", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlO, 251,"Открыть сообщение",252,"Открыть сообщение в JBrowser.")]
        private void ShowMessageIntoJBrowser()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.OpenMessageInJBrowser", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlShiftO, 253,"Открыть сообщение на RSDN",254,"Открыть сообщение на сайте RSDN.")]
        private void ShowMessageIntoExtBrowser()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.OpenMessageOnRsdn", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlShiftC, 255,"Адрес автора сообщения",256,"Скопировать адрес автора сообщения в буфер обмена.")]
        private void CopyAuthorAddress()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.CopyMessageAuthorAddress", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlM, 101,"Модерирование",257,"Открыть модерирование на сайте RSDN.")]
        private void OpenSelfModerate()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.OpenModeratingOnRsdn", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 258,"Оценки",259,"Открыть оценки на сайте RSDN.")]
        private void OpenRating()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.OpenMessageRatingOnRsdn", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 260,"Список оценок",261,"Список оценок пользователя.")]
        private void ShowUserRatingIn()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.ShowUserRatingIn", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 262,"Список всех статей",263,"Список всех статей.")]
        private void ShowAllArticle()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.ShowAllArticles", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 264,"Список статей форума",265,"Список статей форума.")]
        public void ShowArticle()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.ShowForumArticles", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 266,"Список всех ФАКУ",267,"Список всех ФАКУ.")]
        private void ShowAllFaq()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.ShowAllFaq", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.None, 268,"Список ФАКУ форума",269,"Список ФАКУ форума.")]
        private void ShowFaq()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.ShowForumFaq", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlN, 270,"Новое сообщение", 271,"Написать новое сообщение.")]
        private void WriteMessage()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.WriteMessage", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlR, 272,"Ответ", 273,"Ответ на сообщение.")]
        private void ReplyMessage()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.ReplyMessage", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlQ, 274,"Сообщение прочитано",275,"Пометить сообщение как прочитанное.")]
        private void MarkMessagesRead()
        {
            ServiceManager.TryExecuteCommand(
                "Janus.Forum.SetMessagesReadMark",                    //MLHIDE
                new Dictionary<string, object> { { "isRead", true }, { "markChilds", false } }); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlShiftQ, 276,"Сообщение не прочитано",277,"Пометить сообщение как не прочитанное.")]
        public void MarkMessagesUnRead()
        {
            ServiceManager.TryExecuteCommand(
                "Janus.Forum.SetMessagesReadMark",                    //MLHIDE
                new Dictionary<string, object> { { "isRead", false }, { "markChilds", false } }); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlT, 278,"Ответы прочитаны",315,"Помечать ответы на это сообщение как прочитанные.")]
        private void ReadRepliesSingle()
        {
            ServiceManager.TryExecuteCommand(
                "Janus.Forum.SetMessagesReadMark",                    //MLHIDE
                new Dictionary<string, object> { { "isRead", true }, { "markChilds", true } }); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlShiftT, 314,"Ответы не прочитаны",313,"Помечать ответы на это сообщение как непрочитанные.")]
        private void UnreadRepliesSingle()
        {
            ServiceManager.TryExecuteCommand(
                "Janus.Forum.SetMessagesReadMark",                    //MLHIDE
                new Dictionary<string, object> { { "isRead", false }, { "markChilds", true } }); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlY, 312,"Ответы по теме прочитаны",311,"Помечать ответы в эту тему как прочитанные.")]
        private void ReadRepliesAll()
        {
            //ToDo: implement!
        }

        [ml_MethodShortcut(Shortcut.CtrlShiftY, 312,"Ответы по теме прочитаны",311,"Помечать ответы в эту тему как прочитанные.")]
        private void UnreadRepliesAll()
        {
            //ToDo: delete!
        }

        [ml_MethodShortcut(Shortcut.None, 310,"Добавить в Избранное",309,"Добавить сообщение в Избранное")]
        private void AddToFavorites()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.AddForumMessageToFavorites", new Dictionary<string, object>()); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlW, 307,"Тема прочитана",306,"Пометить тему как прочитанную.")]
        private void MarkConvRead()
        {
            ServiceManager.TryExecuteCommand(
                "Janus.Forum.SetTopicReadMark",                       //MLHIDE
                new Dictionary<string, object> { { "isRead", true } }); //MLHIDE
        }

        [ml_MethodShortcut(Shortcut.CtrlShiftW, 305,"Тема не прочитана",304,"Пометить тему как не прочитанную.")]
        private void MarkConvUnRead()
        {
            ServiceManager.TryExecuteCommand(
                "Janus.Forum.SetTopicReadMark",                       //MLHIDE
                new Dictionary<string, object> { { "isRead", false } }); //MLHIDE
        }

        [ml_MethodShortcut((Shortcut)Keys.None, 303,"Предыдущее непрочитанное в теме",302,"Перейти к предыдущему непрочитанному сообщению в текущей теме.")]
        private void SelectHighlightedMsgPrevInCurTipic()
        {
            SelectNodeByAttribute(StepDirection.Up, AttrType.Unread, SearchMessageArea.CurrentTopic);
        }

        [ml_MethodShortcut((Shortcut)(Keys.Control | Keys.Up), 301,"Предыдущее непрочитанное",300,"Перейти к предыдущему непрочитанному сообщению.")]
        private void SelectHighlightedMsgPrev()
        {
            SelectNodeByAttribute(StepDirection.Up, AttrType.Unread, SearchMessageArea.CurrentForum);
        }

        [ml_MethodShortcut(Shortcut.None, 299,"Свернуть и перейти в начало",298,"Свернуть и перейти в корневое сообщение темы.")]
        private void CollapseAndGoRootShortcut()
        {
            CollapseAndGoRoot();
        }

        [ml_MethodShortcut((Shortcut)(Keys.Control | Keys.Down), 297,"Следующее непрочитанное",296,"Перейти к cледующему непрочитанному сообщению.")]
        private void SelectHighlightedMsgNext()
        {
            SelectNodeByAttribute(StepDirection.Down, AttrType.Unread, SearchMessageArea.CurrentForum);
        }

        [ml_MethodShortcut((Shortcut)Keys.None, 295,"Следующее непрочитанное в теме",294,"Перейти к следующему непрочитанному сообщению в просматриваемой теме.")]
        public void SelectHighlightedMsgNextInCurrTopic()
        {
            SelectNodeByAttribute(StepDirection.Down, AttrType.Unread, SearchMessageArea.CurrentTopic);
        }

        [ml_MethodShortcut(Shortcut.None, 293,"Развернуть только непрочитанное",292,"Развернуть только непрочитанные ветки темы.")]
        private void ExpandUnreadShortcut()
        {
            ExpandUnread();
        }

        [ml_MethodShortcut((Shortcut)(Keys.Control | Keys.Shift | Keys.Up), 291,"Предыдущий ответ мне",290,"Перейти к предыдущему непрочитанному сообщению являющемуся ответом на мое сообщение.")]
        private void SelectHighlightedAnswerToMyMsgPrev()
        {
            SelectNodeByAttribute(StepDirection.Up, AttrType.UnreadAnswerToMe, SearchMessageArea.CurrentForum);
        }

        [ml_MethodShortcut((Shortcut)(Keys.Control | Keys.Shift | Keys.Down), 289,"Следующий ответ мне",288,"Перейти к следующему непрочитанному сообщению являющемуся ответом на мое сообщение.")]
        private void SelectHighlightedAnswerToMyMsgNext()
        {
            SelectNodeByAttribute(StepDirection.Down, AttrType.UnreadAnswerToMe, SearchMessageArea.CurrentForum);
        }

        [ml_MethodShortcut((Shortcut)(Keys.Shift | Keys.Up), 287,"Предыдущее помеченное",286,"Перейти к предыдущему помеченному сообщению.")]
        private void SelectMarkedMsgPrev()
        {
            SelectNodeByAttribute(StepDirection.Up, AttrType.Marked, SearchMessageArea.CurrentForum);
        }

        [ml_MethodShortcut((Shortcut)(Keys.Shift | Keys.Down), 285,"Следущее помеченное",284,"Перейти к следующему помеченному сообщению.")]
        private void SelectMarkedMsgNext()
        {
            SelectNodeByAttribute(StepDirection.Down, AttrType.Marked, SearchMessageArea.CurrentForum);
        }

        [ml_MethodShortcut((Shortcut)(Keys.Control | Keys.A ), 283,"Выбрать все сообщения",282,"Выбрать все сообщения в текущем окне.")]
        private void SelectAllMessages()
        {
            ServiceManager.TryExecuteCommand("Janus.Forum.MessageSelectAll", new Dictionary<string, object> {}); //MLHIDE
        }

        #endregion

        private void ClearMessageView()
        {
            if (_msgViewer != null)
            {
                _msgViewer.Msg = null;
            }
        }

        protected void SetMessageView(IMsg message)
        {
            if (_msgViewer != null)
            {
                _msgViewer.Msg = message;
                IUIShell uiShell = _serviceManager.GetRequiredService<IUIShell>();
                uiShell.ActiveGrid = _tgMsgs;
                uiShell.AddRefreshControl(this);
            }
        }

        #region Navigation history support

        private void MessageNavigated(object sender, EventArgs e)
        {
            SetMessageView(ActiveForum.ActiveMsg);

            if (_tgMsgs.ActiveNode != ActiveForum.ActiveMsg)
            {
                _tgMsgs.ActiveNode = ActiveForum.ActiveMsg;
            }

            if (Config.Instance.MarkNavigatedMessages)
            {
                StartMarkTimer();
            }
        }


        #endregion

        #region Обработчики событий объектной модели.

        /// <summary>
        /// Starts the mark timer.
        /// </summary>
        protected void StartMarkTimer()
        {
            _markTimer.Enabled = false;
            _markTimer.Interval = Config.Instance.MarkMessageReadInterval;
            _markTimer.Enabled = true;
        }

        /// <summary>
        /// Handles the Elapsed event of the _markTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
        private void _markTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            _markTimer.Enabled = false;

            try
            {
                IMsg msg = _tgMsgs.ActiveNode as IMsg;

                if (msg != null && msg.IsUnread)
                {
                    if (_tgMsgs.SelectedNodes.Count == 1)
                    {
                        IEnumerable<MsgBase> messages = _tgMsgs.SelectedNodes.Cast<MsgBase>();
                        ForumHelper.MarkMsgRead(ServiceManager, messages, true, false);
                    }
                }
            }
            catch
            {
                // Если не сложилось, пробуем отложить операцию до лучших времен.
                _markTimer.Enabled = true;
            }
        }

        /// <summary>
        /// MSGS_ handling after node activation.
        /// </summary>
        /// <param name="activatedNode">The activated node.</param>
        private void _tgMsgs_AfterActivateNode(ITreeNode activatedNode)
        {
            if (ActionAfterActivateNode(activatedNode))
            {
                InvokeSelectedMessagesChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Selects the message.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        /// <param name="ignoreOldId"></param>
        /// <returns></returns>
        internal virtual bool SelectMessage(IMsg msg, bool ignoreOldId)
        {
            if (!ignoreOldId && msg.ID == OldMsgId)
            {
                return false;
            }

            OldMsgId = msg.ID;

            if (ApplicationManager.Instance.ForumNavigator.NavigationMode || msg == null)
            {
                return false;
            }

            // Баг: http://rsdn.ru/Forum/Message.aspx?mid=2805588&only=1
            //if (msg.ID == _oldMsgID)
            //	return;

            if (ActiveForum == null)
            {
                return false;
            }

            // UNDONE: Сомнительное решение.
            // Если наполнение тригрида не совпадает с текущим форумом,
            // то обновляем наполнение тригрида ...
            // Это мешает реализации фильтрации содержимого тригрида, 
            // да и по идее должно отлавливаться другими способами.
            // Пробуем временно убрать.
            /*
                if (forum.Msgs != _tgMsgs.Nodes)
                {
                    _tgMsgs.Nodes = forum;
                    curMsg = forum.FindMsgByID(msg.ID);
                    if (curMsg == null)
                        return;
                }
                else
                */

            var curMsg = msg;

            // Игрушечная асинхронность, которой на самом деле нет.
            // Однако позволяет не блокировать сразу цикл обработки сообщений,
            // что позволяет быстро перещелкивать сообщения в гриде с клавиатуры
            // без неприятного замедления времени отклика
            AsyncHelper.CreateOperation().PostOperationCompleted(
                () => ApplicationManager.Instance.ForumNavigator
                          .SelectMessage(curMsg.ForumID, curMsg.ID, ignoreOldId)); // force navigation
            return true;
        }

        /// <summary>
        /// Action after activate node.
        /// </summary>
        /// <param name="activatedNode">The activated node.</param>
        /// <returns> true if node can be activated</returns>
        protected virtual bool ActionAfterActivateNode(ITreeNode activatedNode)
        {
            if (activatedNode == null)
            {
                return false;
            }

            IMsg msg = (IMsg)activatedNode;

            if (!SelectMessage(msg, false)) return false;

            //_oldMsgID = curMsg.ID;
            StartMarkTimer();
            return true;
        }


        private void _tgMsgs_MouseUp(object sender, MouseEventArgs e)
        {
            _dragStartDetector.MouseUp(e);

            if ((e.Button & MouseButtons.Right) != 0)
            {
                _contextMenuStrip.Show((Control)sender, e.Location);
            }
        }

        private void _tgMsgs_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            var p = _tgMsgs.PointToClient(Cursor.Position);

            if (CheckMark(p.X, p.Y))
            {
                return;
            }

            ServiceManager.ExecuteDefaultCommand();
        }

        private void _tgMsgs_BeforeMouseDown(object sender, BeforeMouseDownEventArgs e)
        {
            if (CheckMark(e.X, e.Y))
            {
                e.Cancel = true;
            }
        }

        private void _tgMsgs_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            var sort = Config.Instance.ForumSortCriteria;

            sort = TranslateColumnToSortOrder(e.Column, sort);

            if (Config.Instance.ForumSortCriteria == sort)
            {
                return;
            }

            Config.Instance.ForumSortCriteria = sort;

            var forum = Features.Instance.ActiveFeature as Forum;
            if (forum != null)
            {
                forum.Refresh();

                var active = _tgMsgs.ActiveNode as IMsg;

                _tgMsgs.Nodes = forum.Msgs;

                if (active != null)
                {
                    _tgMsgs.ActiveNode = forum.FindMsgById(active.ID);
                }

                ResetFilter();
            }
        }

        protected virtual SortType TranslateColumnToSortOrder(int columnId, SortType sort)
        {
            switch (columnId)
            {
                case 0:
                    sort = sort == SortType.ByIdAsc
                               ? SortType.ByIdDesc
                               : SortType.ByIdAsc;
                    break;
                case 2:
                    sort = sort == SortType.BySubjectAsc
                               ? SortType.BySubjectDesc
                               : SortType.BySubjectAsc;
                    break;
                case 3:
                    sort = sort == SortType.ByAuthorAsc
                               ? SortType.ByAuthorDesc
                               : SortType.ByAuthorAsc;
                    break;
                case 7:
                    sort = sort == SortType.ByLastUpdateDateDesc
                               ? SortType.ByLastUpdateDateAsc
                               : SortType.ByLastUpdateDateDesc;
                    break;
            }

            return sort;
        }

        private void _tgMsgs_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && !CheckMark(e.X, e.Y))
            {
                _dragStartDetector.MouseDown(e);
            }
        }

        private void _tgMsgs_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left || !_dragStartDetector.MouseMove(e))
            {
                return;
            }

            if (_tgMsgs.SelectedNodes.Count > 0)
            {
                var nodes = new TreeNodeBag(_tgMsgs.SelectedNodes);

                try
                {
                    _tgMsgs.DoDragDrop(nodes, DragDropEffects.Copy | DragDropEffects.Link);
                }
                catch (Exception ex)
                {
                    Debug.Print(ex.ToString());
                } //try
            } //if

            _dragStartDetector.Reset();
        }

        private void _tgMsgs_ColumnReordered(object sender, ColumnReorderedEventArgs e)
        {
            Config.Instance.ForumColumnOrder = _tgMsgs.ColumnsOrder;
        }

        private void _tgMsgs_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
        {
            Config.Instance.ForumColumnWidth = _tgMsgs.ColumnsWidth;
        }

        private void _splitter_SplitterMoved(object sender, SplitterEventArgs e)
        {
            Config.Instance.ForumSplitterPosition = _splitter.SplitPosition;
        }

        [EventHandler(ForumEventNames.BeforeForumEntryChanged)]
        private void BeforeForumEntryChanged(ForumEntryChangedEventArgs args)
        {
            _asyncOperation.Send(
                () =>
                {
                    bool isWholeForum;
                    if (ActiveForum == null ||
                        !args.Entries.IsContainsForum(ActiveForum.ID, out isWholeForum))
                        return;

                    if (isWholeForum)
                        _tgMsgs.Nodes = null;
                    StopMarkTimer();
                });
        }

        [EventHandler(ForumEventNames.AfterForumEntryChanged)]
        private void AfterForumEntryChanged(ForumEntryChangedEventArgs args)
        {
            _asyncOperation.Send(
                () =>
                {
                    bool isWholeForum;
                    if (ActiveForum == null ||
                        !args.Entries.IsContainsForum(ActiveForum.ID, out isWholeForum))
                        return;

                    if (isWholeForum)
                    {
                        _tgMsgs.Nodes = ActiveForum.Msgs;
                        _tgMsgs.ActiveNode = ActiveForum.ActiveMsg;
                    }
                    _tgMsgs.Invalidate();
                });
        }

        #endregion

        #region Helper methods
        private int _oldMsgID = -1;

        private void SetPositionViewMsgArea(bool toRight)
        {
            if (toRight)
            {
                _topPanel.Dock = DockStyle.Left;
                _splitter.Dock = DockStyle.Left;
            }
        }

        private void SetMessageMarked(MsgBase msg)
        {
            if (msg == null)
            {
                return;
            }
            _markTimer.Enabled = false;
            msg.SetMessageMarked(!msg.Marked, Config.Instance.SelfId);
            _tgMsgs.Update();
        }

        private static bool IsMsgConformAttribute(IMsg msg, AttrType attrType)
        {
            switch (attrType)
            {
                case AttrType.Any:
                    return true;
                case AttrType.Unread:
                    return msg.IsUnread;
                case AttrType.Marked:
                    return msg.Marked;
                case AttrType.UnreadAnswerToMe:
                    return msg.IsUnread
                           && msg.Parent.UserID > 0
                           && msg.Parent.UserID == Config.Instance.SelfId;
            }

            return false;
        }

        private void DoExpandUnread(IMsg node)
        {
            if (node.HasRepliesUnread)
            {
                _tgMsgs.ExpandNode(node);
            }
            else
            {
                _tgMsgs.CollapseNode(node);
            }

            if (node.HasChildren)
            {
                foreach (IMsg item in node)
                {
                    DoExpandUnread(item);
                }
            }
        }

        private bool CheckMark(int x, int y)
        {
            int column;
            ITreeNode node;

            _tgMsgs.GetNodeAndCellIndexAt(x, y, out node, out column);

            if (column == 1)
            {
                SetMessageMarked((MsgBase)node);
                _tgMsgs.Invalidate();
                _tgMsgs.Update();

                return true;
            }

            return false;
        }

        private IMsg GetActiveMsgList()
        {
            var forum = (Forum)Features.Instance.ActiveFeature;
            return _isFilterActive
                       ? GetFilteredMsgList(ServiceManager, forum.Msgs, _tbxFilter.Text)
                       : forum.Msgs;
        }

        //private static bool MarkItemWithChildTags(ToolStripMenuItem ti, Image markImage)
        //{
        //    var childChecked = false;
        //    foreach (ToolStripItem item in ti.DropDownItems)
        //    {
        //        var child = item as ToolStripMenuItem;
        //        if (child != null && MarkItemWithChildTags(child, markImage))
        //            childChecked = true;
        //    }
        //    if (!ti.Checked && childChecked)
        //        ti.Image = markImage;
        //    return childChecked || ti.Checked;
        //}
        #endregion

        #region Фильтрация по темам в списке сообщений
        private static IMsg GetFilteredMsgList(
            IServiceProvider provider,
            IMsg iMsg,
            string filterText)
        {
            if (iMsg is Msg)
            {
                return Msg.FilterFirstLevel(provider, (Msg)iMsg, filterText);
            }

            return iMsg;
        }

        private void ApplyCurrentFilter()
        {
            ApplyFilter(_tbxFilter.Text);
        }

        private void ApplyFilter(string filterText)
        {
            Forum forum = Features.Instance.ActiveFeature as Forum;

            if (!string.IsNullOrEmpty(filterText))
            {
                if (forum != null)
                {
                    _tgMsgs.Nodes = GetFilteredMsgList(ServiceManager, forum.Msgs, filterText);
                }
            }
            else if (forum != null)
            {
                if (_tgMsgs.Nodes != forum.Msgs)
                {
                    _tgMsgs.Nodes = forum.Msgs;

                    // HACK: Хак, чтобы вернуть выделение ноды.
                    _tgMsgs.ActiveNode = _tgMsgs.ActiveNode;
                }
            }
        }

        private void ResetFilter()
        {
            _lastFilterValue = "";

            if (_tbxFilter.Text != "")
            {
                _tbxFilter.Text = "";
                _tbxFilter.Focus();

                _tgMsgs.Nodes = GetActiveMsgList();

                // HACK: Хак, чтобы вернуть выделение ноды.
                _tgMsgs.ActiveNode = _tgMsgs.ActiveNode;
            }
        }

        [ml_MethodShortcut(Shortcut.F3,280,"Спрятать/Показать панель с фильтром",279,"Спрятать или показать панель для фильтрации тем в форуме.")]
        public void ShowHideFilterPanel()
        {
            if (!_filterPanel.Visible)
            {
                ShowFilterPanel();
            }
            else
            {
                HideFilterPanel();
            }
        }

        private void HideFilterPanel()
        {
            _filterPanel.Visible = false;
            _isFilterActive = false;

            ResetFilter();
        }

        private void ShowFilterPanel()
        {
            _filterPanel.Visible = true;
            _isFilterActive = true;

            _tbxFilter.Focus();
        }

        private void _btnHideFilterPanel_Click(object sender, EventArgs e)
        {
            HideFilterPanel();
        }

        private void _tbxFilter_KeyUp(object sender, KeyEventArgs e)
        {
            if (_lastFilterValue != _tbxFilter.Text)
            {
                ApplyFilter(_tbxFilter.Text);
            }
            _lastFilterValue = _tbxFilter.Text;
        }

        private void _btnResetFilter_Click(object sender, EventArgs e)
        {
            ResetFilter();
        }
        #endregion

        #region Public Members

        public void ExpandUnread()
        {
            var current = (IMsg)_tgMsgs.ActiveNode;

            if (current == null)
            {
                return;
            }

            //IMsg topic = current.Topic;
            //if (topic == null)
            //    return;

            DoExpandUnread(current);
            _tgMsgs.Refresh();
        }

        public void CollapseAndGoRoot()
        {
            var current = (IMsg)_tgMsgs.ActiveNode;
            if (current == null)
            {
                return;
            }

            var topic = current.Topic;

            if (topic == null)
            {
                return;
            }

            _tgMsgs.ActiveNode = topic;

            if ((topic.Flags & NodeFlags.Expanded) == NodeFlags.None)
            {
                return;
            }

            _tgMsgs.CollapseNode(topic);
            _tgMsgs.Refresh();
        }

        public void SmartJump()
        {
            var activeForum = ApplicationManager.Forums.ActiveForum;

            // Если активного форума нет или просматриваемое сообщение
            // еще прокручивается, то ничего не делаем
            if (activeForum == null || _msgViewer.PageDown())
            {
                return;
            }

            // Поведение "Переход по пробелу".
            var behavior = Config.Instance.SmartJumpBehavior;

            // Если необходимо только перейти к следующему сообщению
            if (behavior == SmartJumpBehavior.NextAny)
            {
                SelectNodeByAttribute(StepDirection.Down, AttrType.Any, SearchMessageArea.CurrentForum);
                return;
            }

            // Ищем следующий по списку форум с непрочитанными сообщениями,
            // включая текущий.
            var unreadForum = behavior == SmartJumpBehavior.NextUnreadForum
                                  ? Navigator.FindNextUnreadForum()
                                  : activeForum;

            // Ищем сообщение...
            IMsg unreadMsg = null;
            if (unreadForum != null)
            {
                unreadMsg = unreadForum.FindNextUnreadMsg(activeForum == unreadForum);
            }

            // Если все сообщения в форумах прочитаны, то просто
            // переходим к следующему.
            if (unreadMsg != null)
            {
                if (ActiveForum != unreadForum)
                {
                    Features.Instance.ActiveFeature = unreadForum;
                    Forums.Instance.ActiveForum = unreadForum;
                }

                _tgMsgs.ActiveNode = unreadMsg;
            }
            else
            {
                SelectNodeByAttribute(StepDirection.Down, AttrType.Any, SearchMessageArea.CurrentForum);
            }
        }

        public void StopMarkTimer()
        {
            _markTimer.Enabled = false;
        }

        public void SelectNodeByAttribute(StepDirection dir, AttrType attrType, SearchMessageArea area)
        {
            var activeNode = (IMsg)_tgMsgs.ActiveNode;

            if (activeNode == null)
            {
                return;
            }

            var topic = activeNode.Topic;

            // Ищем в текущей ветке.
            var treeNodes = dir == StepDirection.Down
                                ? TreeGrid.TreeGrid.GetFlatArrayOfSubNodes(topic, activeNode)
                                : TreeGrid.TreeGrid.GetFlatArrayOfSubNodesReverse(topic, activeNode);

            foreach (IMsg msg in treeNodes)
            {
                if (IsMsgConformAttribute(msg, attrType))
                {
                    _tgMsgs.ActiveNode = msg;
                    return;
                }
            }

            // Если не нашли, то ищем темы (корневые ветки) имеющие ответы.
            var isCurrPassed = false;
            var messages = new Msg[_tgMsgs.Nodes.Count];

            _tgMsgs.Nodes.CopyTo(messages, 0);

            if (dir == StepDirection.Up)
            {
                Array.Reverse(messages);
            }

            foreach (IMsg msg in messages)
            {
                // Пропускаем все ветки пока не найдем текущую тему.
                if (!isCurrPassed && msg != topic)
                {
                    continue;
                }

                if (!isCurrPassed)
                {
                    isCurrPassed = true;
                    continue; // Текущую тему тоже нужно пропустить.
                }

                // если топик сменился, а нас просили искать только в текущем топике - выходим
                if (area == SearchMessageArea.CurrentTopic && msg.TopicID != topic.TopicID)
                {
                    break;
                }

                // Если в тему есть 
                var isFound = false;
                switch (attrType)
                {
                    case AttrType.Any:
                        isFound = true;
                        break;
                    case AttrType.Marked:
                        isFound = msg.RepliesMarked > 0 || msg.Marked;
                        break;
                    case AttrType.Unread:
                        isFound = msg.RepliesUnread > 0 || msg.IsUnread;
                        break;
                    case AttrType.UnreadAnswerToMe:
                        isFound = msg.RepliesToMeUnread > 0;
                        break;
                }

                if (isFound)
                {
                    switch (attrType)
                    {
                        case AttrType.Any:
                            break;
                        case AttrType.Marked:
                            isFound = msg.IsMarked;
                            break;
                        case AttrType.Unread:
                            isFound = msg.IsUnread;
                            break;
                        default:
                            isFound = false;
                            break;
                    }

                    if (isFound)
                    {
                        _tgMsgs.ActiveNode = msg;
                        return;
                    }

                    treeNodes = TreeGrid.TreeGrid.GetFlatArrayOfSubNodes(msg);

                    if (dir == StepDirection.Up)
                    {
                        Array.Reverse(treeNodes);
                    }

                    foreach (IMsg subMsg in treeNodes)
                    {
                        if (IsMsgConformAttribute(subMsg, attrType))
                        {
                            _tgMsgs.ActiveNode = subMsg;
                            return;
                        }
                    }

                    throw new ApplicationException(
                        ml.ml_string(316, "Агрегированная информация не соответствует реальному содержанию БД.") +
                        ml.ml_string(317, "Произведите пересчет БД и обратитесь к разработчикам."));
                }
            }

            Beeper.DoBeep();
            return;
        }

        public static string readControlText(Control varControl)
        {
            if (varControl.InvokeRequired)
            {
                return (string)varControl.Invoke(
                  new Func<String>(() => readControlText(varControl))
                );
            }
            else
            {
                string varText = varControl.Text;
                return varText;
            }
        }

        public IEnumerable<IMsg> SelectedMessages
        {
            get
            {
                if (InvokeRequired)
                {
                    return (IEnumerable<IMsg>) Invoke(new Func<IEnumerable<IMsg>>(() => SelectedMessages));
                }
                else
                {
                    IEnumerable<IMsg> result = null;
                    _asyncOperation.Send(() => result = _tgMsgs.SelectedNodes.OfType<IMsg>());
                    return result;
                }
            }
        }

        public int OldMsgId
        {
            get
            {
                return _oldMsgID;
            }

            protected set
            {
                _oldMsgID = value;
            }
        }

        protected ServiceManager ServiceManager
        {
            get { return _serviceManager; }
        }

        public Forum ActiveForum
        {
            get
            {
                return Features.Instance.ActiveFeature as Forum;
            }
            //private set
            //{
            //    _activeForum = value;
            //}
        }

        #endregion

        #region Protected Members

        protected override void OnLoad(EventArgs e)
        {
            SetSetings();

            _splitter.SplitPosition = Config.Instance.ForumSplitterPosition;

            // Create context menu
            _menuGenerator = new StripMenuGenerator(ServiceManager, _contextMenuStrip, "ForumMessage.ContextMenu"); //MLHIDE

            base.OnLoad(e);
        }


        private void InvokeSelectedMessagesChanged(EventArgs e)
        {
            if (SelectedMessagesChanged != null)
            {
                SelectedMessagesChanged(this, e);
            }
        }

        #endregion

    }
}